package com.machine.filling.net.serialPort.util;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.machine.filling.Test.TestCommon;
import com.machine.filling.util.FileUtils;
import com.machine.filling.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android_serialport_api.SerialPort;

/**
 * Created by Administrator on 2018/1/12 0012.
 * 串口工具类
 */

public class SerialPortTool {
    private static final String TAG = "SerialPortTool";
    /**
     * 读取串口数据
     */
    private static final int READ_DATA = 1001;
    /**
     * 串口 读数据 超时
     */
    private static final int PORT_READ_OUT_TIME = 1002;

    /**
     * 串口读取超时时间
     */
    private static final int OUT_TIME = 10 * 1000;
    /**
     * 计数器
     */
    private static int TIMES = 0;

    private String serialNum;//串口号（对应串口路径上的ttyS0）

    private String path = "/dev/";//串口路径 （硬件上管他叫设备名）
    private int baudrate = 9600;//波特率
    public boolean serialPortStatus = false; //是否打开串口标志
    public boolean threadStatus; //线程状态，为了安全终止线程

    public SerialPort serialPort = null;
    public InputStream inputStream = null;
    public OutputStream outputStream = null;

    private int bufferSize = 256;//缓冲区大小

    private static final String freamHead = "7EFE";

    private static final String freamEnd = "0D";
    private boolean isReading;

    public SerialPortTool(String serialNum) {
        this.serialNum = serialNum;
        path = "/dev/" + serialNum;
    }

    public SerialPort getSerialPort() {
        return serialPort;
    }

    /**
     * 打开串口
     *
     * @return serialPort串口对象
     */
    public SerialPort openSerialPort() {
        try {
            serialPort = new SerialPort(new File(path), baudrate, 0);
            this.serialPortStatus = true;
            threadStatus = false; //线程状态

            //获取打开的串口中的输入输出流，以便于串口数据的收发
            inputStream = serialPort.getInputStream();
            outputStream = serialPort.getOutputStream();
            new ReadThread().start(); //开始线程监控是否有数据要接收
        } catch (Exception e) {
            Log.e(TAG, serialNum + " 打开串口异常：" + e.toString());
            return serialPort;
        }
        Log.d(TAG, serialNum + "openSerialPort: 打开串口");
        return serialPort;
    }

    /**
     * 关闭串口
     */
    public void closeSerialPort() {
        try {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }

            this.serialPortStatus = false;
            this.threadStatus = true; //线程状态
            if (serialPort != null)
                serialPort.close();
        } catch (IOException e) {
            Log.e(TAG, serialNum + ": 关闭串口异常：" + e.toString());
            return;
        }
        Log.d(TAG, serialNum + ": 关闭串口成功");
    }

    /**
     * 发送串口指令（字符串）
     *
     * @param dataHex String数据指令
     */
    public void sendSerialPort(String dataHex) {
        Log.d(TAG, serialNum + " 发送数据" + dataHex);
        FileUtils.saveFile(null, "发送命令" + dataHex, true);
        try {
            byte[] sendData = StringUtil.hexStrTobytes(dataHex);
            if (sendData.length > 0) {
                if (outputStream != null) {
                    outputStream.write(sendData);
                    outputStream.write('\n');
//                    outputStream.write('\r' + '\n');
                    outputStream.flush();
                    Log.d(TAG, serialNum + ": 串口数据发送成功");
                } else {
                    Log.d(TAG, serialNum + "sendSerialPort() returned: outputStream=null");
                }
            }
        } catch (IOException e) {
            Log.e(TAG, serialNum + ": 串口数据发送失败：" + e.toString());
        }

    }

    /**
     * 单开一线程，来读数据   (包含拆包,合并.  如果含有两条命令连在一起的情况,需要拆包 ; 如果含有一条命令有多帧,需要合并)
     */
    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();

            String lastMsg = "";//完整帧
            int allMsgLength;

            //判断进程是否在运行，更安全的结束进程
            while (!threadStatus) {
                Log.d(TAG, serialNum + "进入线程run");
                //64   1024
                byte[] buffer = new byte[bufferSize];
                int size; //读取数据的大小
                try {
                    size = readMessage(inputStream, buffer);
                    if (size > 0) {

                        String currMsg = StringUtil.bytesToHexString(buffer).substring(0, 2 * size).toUpperCase();
                        Log.d(TAG + serialNum + "接收到了数据", currMsg);
                        FileUtils.saveFile(null, "读取[" + currMsg + "]", true);
                        if (currMsg != null && (currMsg.startsWith(freamHead.toUpperCase()))) {
                            lastMsg = currMsg;
                            //数据域长度
                            String dataLength = currMsg.substring(12, 14);
                            //帧的总长度
                            allMsgLength = Integer.parseInt(dataLength, 16) + 10;

                            if (allMsgLength == size) {
                                Message msg = Message.obtain();
                                msg.obj = lastMsg;
                                msg.what = READ_DATA;
                                mHandler.sendMessage(msg);
                            } else if (allMsgLength > size) {//并包
                                Log.d(TAG, serialNum + "多帧拼接开始:=======》" + lastMsg);
                                int lastMsgLength = allMsgLength - size;
                                while (lastMsgLength > 0) {
                                    buffer = new byte[bufferSize];
                                    size = readMessage(inputStream, buffer);
                                    String thisRead = StringUtil.bytesToHexString(buffer).substring(0, 2 * size).toUpperCase();
                                    FileUtils.saveFile(null, "多拼读取[" + thisRead + "]", true);
                                    Log.d(TAG, serialNum + "多帧拼接:=======》" + thisRead);
                                    lastMsg = lastMsg + thisRead;
                                    lastMsgLength = lastMsgLength - size;
                                }
                                Log.d(TAG, serialNum + "完成整帧拼接: ===" + lastMsg);
                                Message msg = Message.obtain();
                                msg.obj = lastMsg;
                                msg.what = READ_DATA;
                                mHandler.sendMessage(msg);
                            } else {//拆包
                                //先截取出第一帧数据 发送出去
                                int thisFrameLength = allMsgLength;
                                String frameMsg = lastMsg.substring(0, thisFrameLength * 2);
                                if (frameMsg.endsWith(freamEnd)) {
                                    Message msg = Message.obtain();
                                    msg.obj = frameMsg;
                                    msg.what = READ_DATA;
                                    mHandler.sendMessage(msg);
                                } else {
                                    Log.d(TAG, "拆包时出现拆包第一帧是错误帧(结尾不是\"0D\"): " + frameMsg);
                                }
                                //获取剩余的帧数据
                                String restMsg = lastMsg.substring(thisFrameLength * 2, lastMsg.length());
                                while (restMsg.length() < 14) {
                                    //继续读取下一帧拼接起来,获取帧长度
                                    buffer = new byte[bufferSize];
                                    int nextSize = readMessage(inputStream, buffer);
                                    restMsg = restMsg + StringUtil.bytesToHexString(buffer).substring(0, 2 * nextSize).toUpperCase();
                                }
                                if (!restMsg.startsWith(freamHead)) {
                                    Log.d(TAG, "拆包舍弃: " + restMsg);
                                    continue;
                                }
                                int secondFrameLength = Integer.parseInt(restMsg.substring(12, 14), 16) + 10;//此帧总长度
                                while (secondFrameLength - (restMsg.length() / 2) > 0) {
                                    buffer = new byte[bufferSize];
                                    int nextSize = readMessage(inputStream, buffer);
                                    restMsg = restMsg + StringUtil.bytesToHexString(buffer).substring(0, 2 * nextSize).toUpperCase();
                                }
                                Message msg = Message.obtain();
                                msg.obj = restMsg;
                                msg.what = READ_DATA;
                                mHandler.sendMessage(msg);

                            }

                        } else {
                            lastMsg = currMsg;
                            Log.d(TAG, serialNum + "此帧废弃===》: " + lastMsg);
                        }
                    } else {
                        Log.d(TAG, serialNum + "run() returned: " + "读取到的size <= 0");
                    }
                } catch (Exception e) {
                    Log.e(TAG, serialNum + "run: 数据读取异常：-------------------------------------------------------------------" + e.toString());
                }
            }
        }
    }

    /**
     * 读取数据, 每次读取 都发送一个超时时间的handler消息
     *
     * @param ins
     * @param buf
     * @throws Exception
     */
    public int readMessage(InputStream ins, byte[] buf) throws Exception {
        Message msg = Message.obtain();
        msg.what = PORT_READ_OUT_TIME;
        msg.obj = TIMES++;
        mHandler.sendMessageDelayed(msg, OUT_TIME);

        int size = ins.read(buf);
        //如果串口离线,则会阻塞再上面的方法上; 下面的方法是移除发送的串口离线消息
        mHandler.removeMessages(PORT_READ_OUT_TIME);
//        Log.d(TAG, "readMessage() returned: " + "移除超时msg");
        return size;
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            switch (msg.what) {
                //读取串口数据
                case READ_DATA:
                    String lastMsg = (String) msg.obj;
                    Log.d(TAG, "lastMsg: " + lastMsg);
                    Log.d(TAG, "serialNum: " + serialNum);
                    if (!StringUtil.isNullOrEmpty(serialNum) && !StringUtil.isNullOrEmpty(lastMsg)
                            && onSerialPortDataReceiveListener != null){
                        //末尾校验，长度校验，crc校验
                        onSerialPortDataReceiveListener.onDataReceive(serialNum, lastMsg, System.currentTimeMillis());
                    }
                    break;
                //读取操作与返回数据 时间间隔
                case PORT_READ_OUT_TIME:
                    Log.d(TAG, "handleMessage() returned: " + serialNum + "连接超时");
                    Message outTimeMsg = Message.obtain();
                    outTimeMsg.what = PORT_READ_OUT_TIME;
                    outTimeMsg.obj = System.currentTimeMillis();
                    mHandler.sendMessageDelayed(outTimeMsg, 2000);
                    //todo 此处暂时注释掉;因员工卡加油后,选择支付方式的时候,读取不到任何数据,此时与离线状态一致,容易混淆
//                    onSerialPortDataReceiveListener.onPortOffLineOutTime(DateUtils.getCurrentTimeYMDHMS(), serialNum);
                    break;
            }
        }
    };

    //这是写了一监听器来监听接收数据
    public OnSerialPortDataReceiveListener onSerialPortDataReceiveListener = null;

    public interface OnSerialPortDataReceiveListener {
        /**
         * 串口连接成功,返回数据
         *
         * @param serialNum           串口号
         * @param hexStr              读取到的串口数据
         * @param currentTimeMillions
         */
        void onDataReceive(String serialNum, String hexStr, long currentTimeMillions);

        /**
         * 串口离线
         *
         * @param time
         * @param portNum
         */
        void onPortOffLineOutTime(String time, String portNum);
    }

    public void setOnSerialPortDataReceiveListener(OnSerialPortDataReceiveListener onSerialPortDataReceiveListener) {
        this.onSerialPortDataReceiveListener = onSerialPortDataReceiveListener;
    }

    /**
     * 测试用的 模拟串口数据
     */
    public void testSimulateSerialPort() {
        final Handler handler = new Handler();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                onSerialPortDataReceiveListener.onDataReceive(serialNum, TestCommon.TEST_SERIAL_DATA, System.currentTimeMillis());
                handler.postDelayed(this, 2000);
            }
        };
        handler.postDelayed(runnable, 2000);
    }
}
